The ReaderT Design Pattern
Problem
Globally accessed data
Global variables are bad: 仮にimmutableであっても駄目なのかな?
駄目か、データのアクセス方法に一貫性が保てないから濫用される
and mutable globals are far worse: mutableだとなお悪いと言っているのでimmutableでも駄目だろう
Avoiding WriterT and StateT
Exception-survival : WriterT , StateT は例外が起きた時に状態をロスる
Stateなどは関数の連結でしかないため 途中の状態 というものがない。
runState した時に評価される。そのため途中でfailしても状態自体を保存していないから何も残らない。
これは Writer などでロガーを実装した時にも起こり得る。(多分)
False purity : ここよくわからない
本当の mutation のメリットを受けられていない、とのことだがなぜだ
Concurrency : StateT で更新処理を隠蔽した上で別threadで更新すると意図しない順序で結果が現れることがある, ここもメカニズムがよくわからない
WriterT is broken : Gabrielが説明したようにStrict版のWriterTは space leakする (らしい) 割と自然にWriterT使ってしまいそうなので、避ける理由について納得したい
Writeというアクション自体がpureであれば使ってもいいがIOを伴うような書き込みは基本的に避けた方がよさそう
If you have some subset of your application which can perform no IO but needs some kind of mutable state, absolutely, 100%, please use StateT.
IO無しのmutable stateが必要ならば使えとのこと
Avoiding ExceptT
ExceptT e IO は実質的に何も表していない。何故なら IO の中にはいかなる例外も起きうるから。
具体例として複数のファイルを並行して処理して結果を集約する場合に Reader と TVar の組み合わせは非常によい。
Solution
Just ReaderT
Globally accessed data
アプリケーションの起動時にグローバルなデータを初期化して ENV に含める。
アプリケーションからのアクセスは ReaderT ENV 経由で行う。
更に local を使うとグローバルなデータの派生をローカル専用に用意できる。
Avoiding WriterT and StateT
Avoiding ExceptT
並列処理でのReaderの読み出しについては Control.Concurrent.Async.Lifted.Safe の使い方が重要になる。
Deep monad transformer stacks are confusing: それは確かにそうだ。
ReaderT 一層だけだとGHCにとっても最適化がしやすいらしい
Further
Has type class approach
ではloggingなどをどう実装するか?
所感
FreeとかFreerをアプリケーションのバックボーンに入れてないというのがポイント高い。
いわゆる単純な型クラスの応用でできている。